How this package implements Tauc plots

Taha Ahmed

2021-09-17

Introduction

For a direct band gap semiconductor, such as ZnO, the product of its absorption coefficient \(\alpha\) and incident photon energy \(hv\) is proportional to the square root of the energy difference between its band gap \(E_\text{g}\) and the incident photon energy \(E\):

\[ \alpha hv \propto (E - E_\text{g})^{1/2} \]

which can be transformed based on the definition of absorbance, \(A\), to

\[ (Ahv)^2 \propto E - E_\text{g} \]

To determine the optical band gap of a crystalline semiconductor, the ordinate is given by \(\alpha^{1/r}\), in which the exponent denotes the nature of the transition:

A Tauc plot is usually performed manually, that is, the band edge region is identified visually, and then a linear fit to the linear part of the band edge is performed manually and the intersect with the abscissa is calculated to arrive at the optical band gap.

If you have a large number of spectra, say from a time series experiment, its unusual for the band edge to stay constant over time, e.g., due to changing particle size, and so calculating the band gap by hand for each spectrum quickly becomes tedious.

We have developed a semi-automatic algorithm, that only requires you to supply four x values: two values that define the “plateau” on the low-energy side of the band edge, and two values that define the “plateau” on the high-energy side of the band edge.

This algorithm takes the energy and absorbance of a single spectrum as input, and returns the calculated optical band gap, as well as the fitted Tauc line and its goodness-of-fit parameters (adjusted R-squared and number of points).

Note that this package expects you to supply the UV-Vis spectrum as absorbance (unitless) vs energy (in electronvolts).

This sketch is a visual attempt to describe the algorithm we have developed. The minimum necessary input is highE.limits and lowE.limits, which define the dark green and dark red circles, respectively. Using these x-values, we find all spectral datapoints inside their defined ranges and create two linear fits, that define the “floor” (red line) and “ceiling” (green line) below and above the band edge, respectively. The vertical extent of the band edge can then be calculated (dashed orange line, ceiling2floor), and finally a subset of vertical range, whose extent is defined by the input parameter bg.limits (which defaults to c(0.3, 0.8) of the floor-to-ceiling distance), is used to select the spectral datapoints that are fitted linearly (solid orange line). The intersect of this linear fit with the x-axis is the optical band gap.

This package includes a dataset with 141 UV-Vis spectra from an experiment on growing colloidal ZnO nanoparticles. This data is a subset of the data behind our publication (see Ahmed & Edvinsson, JPCC 2020).

Working with a single spectrum

r            <- 0.5
lowE.limits  <- c(3.0, 3.4)
highE.limits <- c(3.9, 4.3)
bg.limits    <- c(0.3, 0.8)
anim_path    <- here("man/figures/animation.gif")
this.data <- uvvistauc::UVVisDataZnO
# convert wavelength in nm to energy in eV
this.data %<>% mutate(energy = photoec::wavelength2energy(wavelength))
this.spectrum <-
   this.data %>%
   filter(sampleid == "N04H-00000")
spectrum <-
   uvvistauc::tauc(
      energy       = this.spectrum$energy,
      absorbance   = this.spectrum$intensity,
      r            = r,
      lowE.limits  = lowE.limits,
      highE.limits = highE.limits,
      bg.limits    = bg.limits)

Working with multiple spectra

The uvvistauc() takes one one spectrum at a time. The easiest way to work around this intended limitation is to wrap the function call inside a loop.

What follows is a simple demonstration of this approach, using the ZnO dataset included with this package.

We can quickly create a rudimentary plot demonstrating the fitted “ceiling” (green) and “floor” (red) across the whole dataset, as well as the Tauc line itself (orange).

ggplot(spectra, aes(group = sampleid)) +
   coord_cartesian(ylim = c(0, 7), xlim = c(3, 4.4)) +
   geom_line(aes(energy, floor), colour = "red", size = 0.2, alpha = 0.4) +
   geom_line(aes(energy, ceiling), colour = "#008000", size = 0.2, alpha = 0.4) +
   geom_line(aes(energy, abs.2), colour = "black", size = 0.3) +
   geom_line(aes(energy, fit.tauc), colour = "orange", size = 0.3, alpha = 0.6) +
   labs(x = "E/eV", y = paste0("(Abs)<sup>", 1/r, "</sup>")) +
   theme(axis.title.y = element_markdown())

We can also get a quick measure of the goodness of Tauc fit across all spectra by inspecting the statistics of the adjusted R-squared and the number of fitted datapoints:

summary(spectra$fit.adj_rsq)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.9381  0.9867  0.9921  0.9884  0.9948  0.9977
summary(spectra$fit.points)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    12.0    17.0    19.0    20.3    23.0    30.0

The summary statistics can also be plotted, along with the calculated band gap values.

p1 <- ggplot(spectra) +
   geom_point(aes(time, fit.Eg)) +
   theme(axis.text = element_text(size = 11), axis.title.x = element_blank())
p2 <- ggplot(spectra) +
   geom_point(aes(time, fit.adj_rsq)) +
   theme(axis.text = element_text(size = 11), axis.title.x = element_blank())
p3 <- ggplot(spectra) +
   geom_point(aes(time, fit.points)) +
   labs(x = "Time/min") +
   theme(axis.text = element_text(size = 11))
plot_grid(p1, p2, p3, nrow = 3, align = "v")

From all this we can tell that for this dataset, the Tauc fits have a high quality over the entire dataset: R-squared values are very close to unity, and the number of fitted points never dip below 12, mostly staying around 20.

Animated visualisation of this package’s Tauc algorithm

In this animation, we step through each spectrum (black) in our dataset, showing its “ceiling” (green, based on the user-supplied highE.limits) and its “floor” (red, based on the user-supplied lowE.limits), as well as the actual data points that the algorithm selected for fitting (yellow circles), the resultant linear fit (orange), and the intersect of the Tauc fit with the x-axis (large black circle).

I think this animation is really neat, but then I’m partial, of course ;-)

Notes